package com.fxu.framework.core.holder;

import com.fxu.framework.core.exception.MsgException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;

import javax.annotation.Nullable;
import java.util.concurrent.TimeUnit;

/**
 * 单例模式获取 redisTemplate
 * @author fangxu
 * @version 1.0.0
 */
@Slf4j
public class RedisHolder {
    private static RedisTemplate<String, Object> redisTemplate = null;
    public static RedisTemplate<String, Object> getRedisTemplate() {
        if (redisTemplate == null) {
            redisTemplate = SpringHolder.getBean("redisTemplate");
        }
        return redisTemplate;
    }

    private static RedisTemplate<Object, Object> redisObjectTemplate = null;
    public static RedisTemplate<Object, Object> getRedisObjectTemplate() {
        if (redisObjectTemplate == null) {
            redisObjectTemplate = SpringHolder.getBean("redisTemplate");
        }
        return redisObjectTemplate;
    }

    // 防止缓存击穿, 设置缓存内容为null
    private final static String NULL = "fxu_null";

    @FunctionalInterface
    public interface BizFunc<T> {
        /**
         * Gets a result.
         * @return a result
         * @throws Exception 异常
         */
        T process() throws Exception;
    }

    /**
     * 获取redis的值
     * @param redisKey 缓存的key值
     * @param <T>      执行动作范型类型
     * @return 执行动作返回
     */
    public static <T> T getMayNull(String redisKey) {
        if (null == redisKey) return null;

        Object redisVal = getRedisTemplate().opsForValue().get(redisKey);
        if (redisVal != null) {
            return NULL.equals(redisVal.toString()) ? null : (T) redisVal;
        }
        return null;
    }

    /**
     * 使用redis缓存[无限期缓存]
     * @param redisKey 缓存的key值
     * @param bizFunc  执行动作
     * @param <T>      执行动作范型类型
     * @return 执行动作返回
     */
    @Nullable
    public static <T> T getIfNullSet(String redisKey, BizFunc<T> bizFunc) {
        return getIfNullSet(redisKey, 0, null, bizFunc);
    }

    /**
     * 使用redis缓存[有限期缓存]
     * @param redisKey 缓存的key值
     * @param second   过期秒
     * @param bizFunc  执行动作
     * @param <T>      执行动作范型类型
     * @return 执行动作返回
     */
    public static <T> T getIfNullSet(String redisKey, int second, BizFunc<T> bizFunc) {
        return getIfNullSet(redisKey, second, TimeUnit.SECONDS, bizFunc);
    }

    /**
     * 使用redis缓存[有限期缓存]
     * 通用缓存函数，带本地缓存 + 分布式锁 + 降级容错
     * @param redisKey 缓存的key值
     * @param timeout  过期时间
     * @param unit     过期时间单位
     * @param bizFunc  执行动作
     * @param <T>      执行动作范型类型
     * @return 执行动作返回
     */
    @SuppressWarnings("unchecked")
    public static <T> T getIfNullSet(String redisKey, int timeout, TimeUnit unit, BizFunc<T> bizFunc) {
        if (null == redisKey || null == bizFunc) return null;

        Object redisVal = getRedisTemplate().opsForValue().get(redisKey);
        if (redisVal != null) {
            return NULL.equals(redisVal.toString()) ? null : (T) redisVal;
        }

        return setAndBack(redisKey, timeout, unit, bizFunc);
    }

    /**
     * 使用redis缓存[无限期缓存]
     * 通用缓存函数，带本地缓存 + 分布式锁 + 降级容错
     * @param redisKey 缓存的key值
     * @param hashKey  hash的key值
     * @param bizFunc  执行动作
     * @param <T>      执行动作范型类型
     * @return 执行动作返回
     */
    @SuppressWarnings("unchecked")
    public static <T> T getIfNullSet(String redisKey, String hashKey, BizFunc<T> bizFunc) {
        if (null == redisKey || null == bizFunc) return null;

        Object redisVal = getRedisTemplate().opsForHash().get(redisKey, hashKey);
        if (redisVal != null) {
            return NULL.equals(redisVal.toString()) ? null : (T) redisVal;
        }

        return setAndBack(redisKey, hashKey, bizFunc);
    }

    /**
     * 使用redis缓存[有限期缓存]
     * @param redisKey 缓存的key值
     * @param second   过期秒
     * @param bizFunc  执行动作
     * @param <T>      执行动作范型类型
     * @return 执行动作返回
     */
    public static <T> T setAndBack(String redisKey, int second, BizFunc<T> bizFunc) {
        return setAndBack(redisKey, second, TimeUnit.SECONDS, bizFunc);
    }

    /**
     * 使用redis缓存[有限期缓存]
     * @param redisKey 缓存的key值
     * @param timeout  过期时间
     * @param unit     过期时间单位
     * @param bizFunc  执行动作
     * @param <T>      执行动作范型类型
     * @return 执行动作返回
     */
    public static <T> T setAndBack(String redisKey, int timeout, TimeUnit unit, BizFunc<T> bizFunc) {
        if (null == redisKey || null == bizFunc) return null;
        try {
            synchronized (("LOCK:" + redisKey).intern()) { // 本地锁防击穿
                T bean = bizFunc.process();
                // 防止缓存击穿 bean == null ? NULL : bean
                if (timeout > 0 && unit != null) {
                    getRedisTemplate().opsForValue().set(redisKey, bean == null ? NULL : bean, timeout, unit);
                } else {
                    getRedisTemplate().opsForValue().set(redisKey, bean == null ? NULL : bean);
                }
                return bean;
            }
        } catch (Exception e) {
            log.error("Redis缓存异常", e);
            throw new MsgException("Redis缓存异常!");
        }
    }

    /**
     * 使用redis缓存[无限期缓存]
     * @param redisKey 缓存的key值
     * @param hashKey  hash的key值
     * @param bizFunc  执行动作
     * @param <T>      执行动作范型类型
     * @return 执行动作返回
     */
    public static <T> T setAndBack(String redisKey, String hashKey, BizFunc<T> bizFunc) {
        if (null == redisKey || null == bizFunc) return null;
        try {
            synchronized (("LOCK:" + redisKey).intern()) { // 本地锁防击穿
                T bean = bizFunc.process();
                // 防止缓存击穿 bean == null ? NULL : bean
                getRedisTemplate().opsForHash().put(redisKey, hashKey, bean == null ? NULL : bean);
                return bean;
            }
        } catch (Exception e) {
            log.error("Redis缓存异常", e);
            throw new MsgException("Redis缓存异常!");
        }
    }
}